﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace DarkFrameMaster
{
	public partial class Form1 : Form
	{
		public static bool isDebug;									//是否DEBUG模式
		public static Panel debugPanel;								//DEBUG日志面板
		public static Label debugLabel;								//DEBUG Label容器

		public static Button[,] mapBD = new Button[15,15];			//15*15的按钮数组

		public static Stream temp;									//当前项目未实装
		public static Stream temp2;									//

		public static int[,] mapStatus = new int[15, 15];			//地图状态
		public static int[,] mapBoom = new int[15, 15];				//地雷分布
		public static int[,] mapOpened = new int[15, 15];			//点开的雷区分部
		public static int boomCount = 0;							//总地雷数量
		public static bool firstTap = true;							//是否第一次点击
		public static int opened = 0;								//点开的单元格数

		static Socket clientSocket;									//套接字保存

		public bool recFlag = false;								//ONLINE MODE 判断是否接收服务器消息的数据（防止刷屏）

		public static byte[] m_DataBuffer = new byte[10240];		//保存服务器发送过来的数据

		public Form1(bool debugMode)
		{
			isDebug = debugMode;									//DEBUG标志由上层传递过来
			InitializeComponent();
		}

		/// <summary>
		/// 初始化雷区
		/// </summary>
		/// <param name="boomNum">预设地雷数量</param>
		/// <param name="cx">玩家第一次点击的位置</param>
		/// <param name="cy">玩家第一次点击的位置</param>
		/// <returns>雷图是否生成成功</returns>
		public bool InitMapBoom(int boomNum, int cx, int cy)
		{
			boomCount = boomNum;
			//预设地雷数量高于单元格数或等于单元格数则生成失败
			if (boomNum >= 15 * 15)
			{
				return false;
			}
			doDebug("生成雷图");

			Random rd = new Random();
			//随机生成预设数量个数字
			for (int i = 0; i < boomNum; i++)
			{
				bool hasSet = false;
				while (hasSet==false)
				{
					int boom = rd.Next(0, 15 * 15);
					int x = (int)(boom / 15);
					int y = boom % 15;

					//如果生成的地雷在第一次点击的位置，则放弃设置该位置为地雷
					if (x==cx && y == cy)
						continue;
					//如果已经生成了地雷则放弃本轮生成
					if(mapBoom[x,y] == 9)
						continue;

					//通过判断，将设置成功标志加入

					hasSet = true;
					for(int xx = 0; xx < 3; xx++)
					{
						if (x - 1 + xx < 0 || x - 1 + xx >= 15) continue;
						for(int yy = 0; yy < 3; yy++)
						{
							int nx = x - 1 + xx;
							int ny = y - 1 + yy;
							if (y - 1 + yy < 0 || y - 1 + yy >= 15) continue;
							if (xx == 1 && yy == 1)
							{
								//将当前点设置为地雷
								mapBoom[nx, ny] = 9;
							}
							if (mapBoom[nx, ny] != 9)
							{
								//周围的单元格除雷区外全部数值+1
								mapBoom[nx, ny] += 1;
							}
						}
					}
				}
			}
			doDebug("生成雷图完毕");
			return true;
		}

		/// <summary>
		/// 点击雷区后的各项判断
		/// </summary>
		/// <param name="x">点击位置X</param>
		/// <param name="y">点击位置Y</param>
		/// <returns>不同的状态码</returns>
		public int CheckWinOrDie(int x, int y)
		{
			//不再扫雷范围直接返回
			if (x >= 15 || x < 0 || y >= 15 || y < 0) return 1;
			//将扫描范围设置为开启
			mapOpened[x, y] = mapBoom[x, y];
			//关闭当前位置的可点击
			mapBD[x, y].Enabled = false;
			//修改单元格颜色
			mapBD[x, y].BackColor = Color.AliceBlue;
			//增加一个点开数量
			opened += 1;
			//如果点到雷区返回0 游戏结束
			if (mapBoom[x, y] == 9)
			{
				mapBD[x, y].Text = "囧";
				return 0;
			}
			if (mapBoom[x, y] == 0)
			{
				//点到0（周围没有雷）就递归调用自己扫描周围的八个格子
				mapBD[x, y].Text = " ";
				for (int xx = 0; xx < 3; xx++)
				{
					for(int yy = 0; yy < 3; yy++)
					{
						int newX = x - 1 + xx;
						int newY = y - 1 + yy;
						if ((newX != x || newY != y) && newX >= 0 && newY >= 0 && newX < 15 && newY < 15)
						{
							if (mapOpened[newX, newY] != -1)
							{
								continue;
							}
							CheckWinOrDie(newX, newY);
						}
					}
				}
			}
			else
			{
				mapBD[x, y].Text = "" + mapBoom[x, y];
			}
			if(15*15-boomCount == opened)
			{
				return 2;
			}
			return 1;
		}

		/// <summary>
		/// 游戏结束操作
		/// </summary>
		/// <param name="win">传递过来赢输标志</param>
		public void GameOver(bool win)
		{
			if (win)
			{
				MessageBox.Show("WIN");
			}
			else
			{
				for (int i = 0; i < 15; i++)
				{
					for (int j = 0; j < 15; j++)
					{
						mapBD[i, j].Text = "" + mapBoom[i, j];
					}
				}
				MessageBox.Show("LOSE");
			}
			DoMyInit();
		}

		/// <summary>
		/// 事件触发，当窗体加载时执行
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Form1_Load(object sender, EventArgs e)
		{
			//判断是否是DEBUG模式，通过三元运算生成不同的标题
			this.Text = (isDebug ? "[DEBUG MODE] " : "") + "扫雷";
			//如果是DEBUG模式，需要生成DEBUG面板用于展示DEBUG信息
			if (isDebug)
			{
				this.Size = new Size(this.Size.Width + 200, this.Size.Height);
				this.mainPanel.Size = new Size(this.mainPanel.Size.Width + 200, this.mainPanel.Size.Height);
				debugPanel = new System.Windows.Forms.Panel();
				debugPanel.Location = new Point(600, 0);
				debugPanel.Size = new Size(200, 450);
				debugPanel.BackColor = Color.White;
				this.mainPanel.Controls.Add(debugPanel);

				debugLabel = new System.Windows.Forms.Label();
				debugLabel.Location = new Point(0, 0);
				debugLabel.Size = new Size(200, 450);
				debugLabel.BackColor = Color.White;
				debugLabel.ForeColor = Color.Black;
				debugLabel.BorderStyle = BorderStyle.Fixed3D;
				debugPanel.Controls.Add(debugLabel);

				doDebug("正在进入DEBUG模式");
				doDebug("进入DEBUG模式成功");
			}
			//
			try
			{
				//读取文件，将文件的数据保存
				temp = File.Open("./logo.png", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
				temp2 = File.Open("./logo2.png", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
				for (int i = 0; i < 15; i++)
				{
					for (int j = 0; j < 15; j++)
					{
						//动态生成和实例化按钮对象
						mapBD[i, j] = new System.Windows.Forms.Button();
						if (mapBD[i, j] != null)
						{
							//动态设置尺寸和位置，并设置背景
							mapBD[i, j].Size = new Size(450 / 15, 450 / 15);
							mapBD[i, j].Location = new Point(j * (450 / 15), i * (450 / 15));
							mapBD[i, j].BackColor = Color.Empty;
							//mapBD[i, j].BackgroundImage = Image.
							mapBD[i, j].BackgroundImageLayout = ImageLayout.Zoom;
							//给每个按钮都添加Click事件（同样的事件）
							mapBD[i, j].Click += new EventHandler(testClick);
							//将按钮加入容器gamePanel中
							this.gamePanel.Controls.Add(mapBD[i, j]);
							//将当前按钮的各项地图数据初始化
							mapStatus[i, j] = 0;
							mapBoom[i, j] = 0;
							mapOpened[i, j] = -1;
							//doDebug("图片控件生成成功！x:" + i + " y:" + j);
						}
						else
						{
							doDebug("图片控件生成失败！");
						}
					}
				}
			}
			catch
			{
				doDebug("图片加载失败！");
			}
			//*/
		}

		/// <summary>
		/// 重置游戏数据
		/// </summary>
		public void DoMyInit()
		{
			doDebug("初始化");
			for (int i = 0; i < 15; i++)
			{
				for (int j = 0; j < 15; j++)
				{
					if (mapBD[i, j] != null)
					{
						//游戏开始标志重置为true
						firstTap = true;
						//还原按钮状态
						mapBD[i, j].Text = "";
						mapBD[i, j].BackColor = Color.Empty;
						mapBD[i, j].Enabled = true;
						//mapBD[i, j].BackgroundImage = Image.
						mapBD[i, j].BackgroundImageLayout = ImageLayout.Zoom;
						//mapBD[i, j].Click += new EventHandler(testClick);
						//this.gamePanel.Controls.Add(mapBD[i, j]);

						//还原地图和游戏数据
						boomCount = 0;
						opened = 0;
						mapStatus[i, j] = 0;
						mapBoom[i, j] = 0;
						mapOpened[i, j] = -1;

						//doDebug("图片控件生成成功！x:" + i + " y:" + j);
					}
					else
					{
						doDebug("图片控件生成失败！");
					}
				}
			}
		}

		/// <summary>
		/// 点击事件
		/// </summary>
		/// <param name="sender">点击事件触发对象</param>
		/// <param name="e"></param>
		public void testClick(object sender, EventArgs e)
		{

			Button obj = sender as Button;
			//遍历找到按钮所在位置
			for (int i = 0; i < 15; i++)
			{
				for (int j = 0; j < 15; j++)
				{
					if (mapBD[i, j] == obj)
					{
						doDebug("点击->x:" + i + ", y:" + j);
						//判断游戏模式
						if (this.GameMode.Text == "本地游戏")
						{
							//判断是否需要生成新的地图
							if (firstTap == true)
							{
								//生成新的地图，传递进第一次点击点（避开）
								InitMapBoom(15, i, j);
								string boom = "";
								for (int ii = 0; ii < 15; ii++)
								{
									for (int jj = 0; jj < 15; jj++)
									{
										boom += (" " + mapBoom[ii, jj]);
									}
									boom += "\n";
								}
								doDebug(boom, false);
								//关闭firstTap标志
								firstTap = false;
							}
							//执行点击判断
							int ret = CheckWinOrDie(i, j);

							//判断游戏状态
							if (ret == 0)
							{
								GameOver(false);
							}
							if (ret == 2)
							{
								GameOver(true);
							}
							/*/
							if (mapStatus[i, j] == 1)
							{
								obj.BackgroundImage = Image.FromStream(temp2);
								mapStatus[i, j] = 2;
							}
							else
							{
								obj.BackgroundImage = Image.FromStream(temp);
								mapStatus[i, j] = 1;
							}
							//*/
						}
						else
						{
							//SendMessage(i, j);
							//实例化DoMessage对象（数据处理）
							DoMessage ms = new DoMessage();
							//将点击事件整合为json组发送
							string json = ms.DoClick(i, j);
							DoSendMsg(json);
						}
					}
				}
			}
			
		}


		/// <summary>
		/// 游戏模式点击事件
		/// </summary>
		/// <param name="sender">游戏模式切换对象</param>
		/// <param name="e"></param>
		public void label1_Click(object sender, EventArgs e)
		{
			//通过判断文本内容变更游戏模式
			if(this.GameMode.Text == "本地游戏")
			{
				this.GameMode.Text = "线上模式";
				//尝试Socket链接，ret保存结果
				bool ret = OnlineGaming();
				//如果失败将模式切换回本地
				if (!ret)
				{
					this.GameMode.Text = "本地游戏";
					showLog("线上模式连接失败！");
				}
			}
			else
			{
				//关闭Socket连接
				clientSocket.Shutdown(SocketShutdown.Both);
				clientSocket.Close();
				this.GameMode.Text = "本地游戏";
			}
		}


		/// <summary>
		/// 委托型处理DoDebug
		/// </summary>
		/// <param name="msg">debug文本消息</param>
		/// <param name="isShow">状态</param>
		public void showLog(string msg, bool isShow = true)
		{
			this.Invoke((EventHandler)(delegate { doDebug(msg, isShow); }));
		}

		/// <summary>
		/// Socket链接
		/// </summary>
		/// <returns>链接状态</returns>
		public bool OnlineGaming()
		{
			IPAddress ip = IPAddress.Parse("47.254.39.166");//IPAddress.Loopback;
			clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

			try
			{
				clientSocket.Connect(new IPEndPoint(ip, 8099));
				showLog("连接服务器成功");
			}
			catch (Exception ex)
			{
				showLog("连接服务器失败");
				showLog(ex.Message);
				return false;
			}

			Thread receive = new Thread(ReceiveMsg);
			receive.IsBackground = true;
			receive.Start(clientSocket);
			/*
			Thread send = new Thread(SendMsg);
			send.IsBackground = true;
			send.Start(clientSocket);
			*/
			return true;
		}

		/// <summary>
		/// OnlineMode接受服务器发送的数据
		/// </summary>
		/// <param name="oClientSocket"></param>
		public void ReceiveMsg(Object oClientSocket)
		{
			Socket clientSocket = oClientSocket as Socket;
			//开启接收标志
			recFlag = true;
			string msg = "";
			Boolean helfM = false;
			int msgLen = 0;
			while (recFlag)
			{
				try
				{
					//接受消息处理
					int receiveLength = clientSocket.Receive(m_DataBuffer);
					string tmsg = "" + Encoding.UTF8.GetString(m_DataBuffer, 0, receiveLength);

					msg += tmsg;

					msgLen = Int32.Parse(msg.Substring(0, 8));
					
					if(msgLen + 8 > msg.Length)
					{
						helfM = true;
					}

					if (helfM == false)
					{
						string actionMsg = "";

						if(msg.Length == msgLen + 8)
						{
							actionMsg = msg.Substring(8);
							msg = "";
						}
						else {
							actionMsg = msg.Substring(8, 8 + msgLen);
							msg = "" + msg.Substring(8 + msgLen);
						}

						bool ret = DoAction(actionMsg);

						//如接收到未预料到的消息或处理中出现异常则进行安全离线操作切换回本地游戏
						if (ret == false)
						{
							showLog("未知错误");
							DoOffLine();
						}
					}
				}
				catch
				{
					//接受消息时发生错误切换回本地游戏
					//this.Invoke((EventHandler)(delegate{ doDebug("Error");}));
					showLog("Error");
					DoOffLine();
				}
			}
		}

		/*
		public static void SendMsg(Object oClientSocket)
		{
			Socket clientSocket = oClientSocket as Socket;
			while (true)
			{
				try
				{
					string sendMessage = Console.ReadLine();
					clientSocket.Send(Encoding.UTF8.GetBytes(sendMessage));
					//Console.WriteLine("向服务器发送消息:{0}", sendMessage);
				}
				catch
				{
					clientSocket.Shutdown(SocketShutdown.Both);
					clientSocket.Close();
					break;
				}
			}
		}
		*/

		/// <summary>
		/// 向服务器发送消息
		/// </summary>
		/// <param name="msg">消息字符串</param>
		public void DoSendMsg(String msg)
		{
			try
			{
				string sendMessage = msg;
				clientSocket.Send(Encoding.UTF8.GetBytes(sendMessage));
				showLog("向服务器发送消息:" + sendMessage, false);
			}
			catch
			{
				//发送失败则进入离线模式
				DoOffLine();
			}
		}

		/// <summary>
		/// 安全的进入离线模式
		/// </summary>
		public void DoOffLine()
		{
			//关闭接收数据标志，让接收线程退出
			recFlag = false;
			if(clientSocket != null && clientSocket.Connected)
			{
				clientSocket.Shutdown(SocketShutdown.Both);
				clientSocket.Close();
			}
			//委托事件将游戏模式改回本地
			this.Invoke((EventHandler)(delegate { this.GameMode.Text = "本地游戏"; }));
			//this.GameMode.Text = "本地游戏";
		}
		//
		//*/

		/// <summary>
		/// 输出日志
		/// </summary>
		/// <param name="demsg">日志信息</param>
		/// <param name="isShow">全部显示</param>
		public void doDebug(string demsg, bool isShow = true)
		{
			string msg = demsg;
			string tmsg = "[" + DateTime.Now.ToString() + "]\n" + msg;
			if (isDebug && isShow)
			{
				debugLabel.Text = tmsg + "\n" + debugLabel.Text;
			}
			Console.WriteLine(tmsg);
			//Console.WriteLine("[Console Log]" + debugLabel.Text);
			return;
		}

		/// <summary>
		/// 根据接受到的服务器消息进行处理
		/// </summary>
		/// <param name="msg">json数组</param>
		/// <returns></returns>
		public bool DoAction(string msg)
		{
			try
			{
				showLog(msg);
				JObject jo = (JObject)JsonConvert.DeserializeObject(msg);
				string action = jo["action"].ToString();
				showLog("DO:" + jo["action"].ToString());
				//this.Invoke((EventHandler)(delegate { this.GameMode.Text = "本地游戏"; }));
				switch (action)
				{
					case "Server_StartNew":this.Invoke((EventHandler)(delegate { DoMyInit(); })) ; break;
					case "Server_UpdateGame":
						JArray update = JArray.Parse(jo["data"].ToString());
						this.Invoke((EventHandler)(delegate { UpdateGame(update); }));
						break;
					case "Server_ClickResult":
						JArray ja = JArray.Parse(jo["pointNum"].ToString());
						this.Invoke((EventHandler)(delegate { ShowClickResult(ja); }));
						break;
					//ShowClickResult(jo);break;
					case "Server_ShowAllBlock":
						JArray showAll = JArray.Parse(jo["data"].ToString());
						this.Invoke((EventHandler)(delegate { UpdateGame(showAll, true); }));
						break;
					case "Server_RoundResult":
						bool status = (bool)jo["status"];
						this.Invoke((EventHandler)(delegate { GameOver(status); }));
						break;
					default:break;
				}
				return true;
			}
			catch
			{
				return false;
			}
		}

		/// <summary>
		/// 更新游戏数据
		/// </summary>
		/// <param name="data">游戏数据</param>
		/// <param name="isShowAll">是否是游戏结束的更新（暂时未启用）</param>
		/// <returns></returns>
		public bool UpdateGame(JArray data, bool isShowAll = false)
		{
			//更新数据前重置数据（防止本地游戏数据残留）
			DoMyInit();
			for(int i = 0; i < 15; i++)
			{
				for(int j = 0; j < 15; j++)
				{
					//将展开的地图数据复刻到本地
					mapOpened[i, j] = (int)data[i][j];
					/*/
					if (isShowAll)
					{

					}
					else
					{
					//*/
						if (mapOpened[i, j] != -1)
						{
							mapBD[i, j].Enabled = false;
							mapBD[i, j].BackColor = Color.AliceBlue;
							mapBD[i, j].Text = mapOpened[i, j] == 0 ? "" : "" + mapOpened[i, j];
							//showLog("  " + mapOpened[i, j]);
						}
					//}
				}
			}
			return true;
		}

		/// <summary>
		/// 根据服务器消息展示点击后的显示结果
		/// </summary>
		/// <param name="obj">json组（数据为单元格显示数据）</param>
		/// <returns>更新成功失败标志</returns>
		public bool ShowClickResult(JArray obj)
		{

			//showLog("开始查找结果" + obj.ToString());
			for (int i = 0; i < 10; i++)
			{
				//showLog("开始查找结果" + i);
				//List<int> list = ob[i] as List<int>;
				//List<int> ll = obj[i]
				//List<int> list = (List<int>)ll;
				if (obj[i] == null || obj[i].ToString() == "null" )
				{
					continue;
				}
				if (obj[i] != null)
				{
					foreach (var point in obj[i])
					{
						int pt = (int)point;
						int x = pt / 15;
						int y = pt % 15;
						mapOpened[x, y] = i;
						mapBD[x, y].Enabled = false;
						mapBD[x, y].BackColor = Color.AliceBlue;
						mapBD[x, y].Text = mapOpened[x, y] == 0 ? "" : "" + mapOpened[x, y];
					}
				}
			}
			return true;
		}
	}
}
